package org.manon.json.asm;

import static org.manon.json.asm.ASMUtil.TYPE_SERIALIZER_PROVIDER;
import static org.manon.json.asm.ASMUtil.TYPE_VOID;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.manon.json.JSONException;
import org.manon.json.meta.Entity;
import org.manon.json.meta.MetaPropertyCollection;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * @author <a href="mailto:jacques.militello@gmail.com">Jacques Militello</a>
 */
public final class ASMContextSerializer implements ASMContext, Cloneable {

	private final ASMGenerator4Serializer _serializer;
	private final Entity _entity;
	private final ClassWriter _classWriter;
	private final Set<Class<?>> _serializerAppended = new HashSet<Class<?>>();
	private final List<ASMAppendToMethod> _fieldToAppend = new ArrayList<ASMAppendToMethod>();
	private final String _className;
	private int _stackCount;
	private int _flagCount;
	private Set<String> _fields = new HashSet<String>();
	
	public ASMContextSerializer(ASMGenerator4Serializer serializer,ClassWriter classWriter,Entity entity) {
		_serializer = serializer;
		_classWriter = classWriter;
		_entity = entity;
		_stackCount = 5;
		_className = entity.getInternalClassNameForSerializer();
	}

	public Set<String> getFields() {
		return _fields;
	}

	public void appendToConstructor(MethodVisitor methodVisitor) {
		if (_fieldToAppend.isEmpty()) {
			return;
		}
		for (ASMAppendToMethod item : _fieldToAppend) {
			item.append(methodVisitor);
		}
	}
	
	public ClassWriter getClassWriter() {
		return _classWriter;
	}

	public int incrementAndGetStack() {
		return ++_stackCount;
	}
	
	public void setStackCount(int stackCount) {
		_stackCount = stackCount + 1;
		_flagCount = stackCount + 1;
	}
	
	public int getFlagCount() {
		return _flagCount;
	}

	public int getStackCount() {
		return _stackCount;
	}
	
	public Entity getEntity() {
		return _entity;
	}
	
	public String getClassName() {
		return _className;
	}

	
	public void appendToConstructor(final Entity entity) {
		_fieldToAppend.add(new ASMAppendToMethodImpl(_className,entity.getInternalClassNameForSerializer(),entity.getName().toLowerCase()));
	}
	
	public void appendToConstructor(final MetaPropertyCollection property,Class<?> target) {
		
		final String targetClassName;
		
		if (property.getCollectionItemSerializer() != null) {
			targetClassName = Type.getInternalName(property.getCollectionItemSerializer());
		} else {
			
			Entity targetEntity = _serializer.findEntityClass(target);
			if (targetEntity.getCustomSerializer() != null) {
				targetClassName = Type.getInternalName(targetEntity.getCustomSerializer());
			} else {
				targetClassName = targetEntity.getInternalClassNameForSerializer();
			}
		}
		
		_fieldToAppend.add(new ASMAppendToMethodImpl(_className,targetClassName,property.getCollectionItemClass().getSimpleName().toLowerCase()));
	}
	
	public void appendToConstructor(Class<?> target) {
		
		if (_serializerAppended.contains(target)) {
			return;
		}
		
		final Entity targetEntity = _serializer.findEntityClass(target);
		
		if (targetEntity == null) {
			throw new JSONException("Failed to find JSON entity for this Class =[" + target + "]");
		}
		
		
		final String targetClassName;
		
		if (targetEntity.getCustomSerializer() != null) {
			targetClassName = Type.getInternalName(targetEntity.getCustomSerializer());
		} else {
			targetClassName = targetEntity.getInternalClassNameForSerializer();
		}
		
		_fieldToAppend.add(new ASMAppendToMethodImpl(_className,targetClassName,target.getSimpleName().toLowerCase()));
		
	}
	
//	public void appendToConstructor(final MetaProperty property,Class<?> target) {
//		
//		final String targetClassName;
//		
//		if ((MetaPropertyType.COLLECTION == property.getType()) && ((MetaPropertyCollection)property).getCollectionItemSerializer() != null) {
//			targetClassName = Type.getInternalName(((MetaPropertyCollection)property).getCollectionItemSerializer());
//		} else {
//			if (_serializerAppended.contains(target)) {
//				return;
//			}
//			
//			Entity targetEntity = _serializer.findEntityClass(target);
//			
//			if (targetEntity == null) {
//				throw new JSONException("Failed to find JSON entity for this Class =[" + target + "]");
//			}
//			
//			
//			
//			if (targetEntity.getCustomSerializer() != null) {
//				targetClassName = Type.getInternalName(targetEntity.getCustomSerializer());
//			} else {
//				targetClassName = targetEntity.getInternalClassNameForSerializer();
//			}
//		}
//		
//		if (property instanceof MetaPropertyCollection) {
//			_fieldToAppend.add(new ASMAppendToMethodImpl(_className,targetClassName,((MetaPropertyCollection)property).getCollectionItemClass().getSimpleName().toLowerCase()));
//		} else {
//			_fieldToAppend.add(new ASMAppendToMethodImpl(_className,targetClassName,property.getReturnType().getSimpleName().toLowerCase()));	
//		}
//		
//		
//		_serializerAppended.add(target);
//	}
	
	
	private static final class ASMAppendToMethodImpl implements ASMAppendToMethod {
		
		private final String _className;
		private final String _targetClassName;
		private final String _property;
		
		ASMAppendToMethodImpl(String className,String targetClassName, String property) {
			_className = className;
			_targetClassName = targetClassName;
			_property = property;
		}

		/**
		 * @see org.manon.json.asm.ASMAppendToMethod#append(org.objectweb.asm.MethodVisitor)
		 */
		public void append(MethodVisitor methodVisitor) {
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
			methodVisitor.visitTypeInsn(Opcodes.NEW, _targetClassName);
			methodVisitor.visitInsn(Opcodes.DUP);
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
			methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,_targetClassName,"<init>",Type.getMethodDescriptor(TYPE_VOID, TYPE_SERIALIZER_PROVIDER));
			methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, _className, _property+ASMGenerator4Serializer.FIELD_SUFFIX,ASMUtil.getDescriptor(_targetClassName));
		}
	}


	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}


}
